<?php

namespace App\Http\Controllers\Wechat;

use App\Http\Controllers\ApiController;
use App\Http\Middleware\WechatAuth;
use App\Http\Requests\Wechat\User\PatchAliAccountRequest;
use App\Http\Requests\Wechat\User\PatchLocationRequest;
use App\Http\Requests\Wechat\User\PatchPhoneRequest;
use App\Http\Requests\Wechat\User\WechatUserUpdateRequest;
use App\Http\Requests\Wechat\WechatUserShowRequest;
use App\Models\OpenWechat\ScanEvent;
use App\Models\OpenWechat\WechatUser;
use App\Models\Store\SkinRecord;
use App\Models\Store\Store;
use App\Models\Store\StorePivotWechatUser;
use App\Services\BaiduGeoService\MapApi;
use App\Services\BaiduGeoService\GeoApiRespMock;
use App\Services\Locale\Language;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;

class WechatUserController extends ApiController
{
    const ROUTES = [
        'index'             => 'wechat.wechat-user.index',
        'show'              => 'wechat.wechat-user.show',
        'store'             => 'wechat.wechat-user.store',
        'update'            => 'wechat.wechat-user.update',
        'patch-location'    => 'wechat.wechat-user.patch-location',
        'destroy'           => 'wechat.wechat-user.destroy',
        'patch-phone'       => 'wechat.wechat-user.patch.phone',
        'patch-ali-account' => 'wechat.wechat-user.patch.ali.account',
    ];

    /**当前登录微信用户详情
     * @test
     *
     * @param WechatUserShowRequest $request
     * @return array|Response
     */
    public function show(WechatUserShowRequest $request)
    {
        $wechat_user = WechatAuth::$wechat_user;

        $wechat_user->append(['age']);

        return $this->response(compact('wechat_user'));
    }

    /**
     * @deprecated
     *
     * @param Request $request
     * @return array|Response
     */
    public function index(Request $request)
    {
        $wechat_user = WechatAuth::$wechat_user;

        $wechat_user_store_ids = $wechat_user->skinRecords()->pluck('store_id');

        $go_stores = $this->getGoStoresGroupByStoreId($wechat_user_store_ids, $wechat_user);

        /** @var LengthAwarePaginator $stores */
        $stores = (new Store)
            ->whereIn('stores.id', $wechat_user_store_ids)
            ->select(['id', 'name', 'store_img'])
            ->paginate($request->per_page ?: 15);

        $stores->each(function (Store $store) use ($go_stores) {
            $hm_skin_count = $go_stores[$store->id][SkinRecord::HM][0]['skin_record_count'] ?? 0;
            $wax_skin_count = $go_stores[$store->id][SkinRecord::WAX][0]['skin_record_count'] ?? 0;
            $store->hm_skin_count = $hm_skin_count;
            $store->wax_skin_count = $wax_skin_count;
            $store->all_skin_count = $wax_skin_count + $hm_skin_count;
        });

        $stores = $stores->sortByDesc('all_skin_count')->values()->all();

        return $this->response(compact('stores'));

    }

    /**
     * 更新当前用户的信息
     * @test
     *
     * @param WechatUserUpdateRequest $request
     * @return array|Response
     * @throws \Exception
     */
    public function update(WechatUserUpdateRequest $request)
    {
        /** @var WechatUser $wechat_user */
        $wechat_user = WechatAuth::$wechat_user;

        $wechat_user_data = $this->assembleWechatUser($request);

        $update = $wechat_user->update($wechat_user_data);

        if ($request->scan_event_id) {
            $this->updateUserLoginScanEvent($request->scan_event_id, $wechat_user->id);
        }

        return $this->response(compact('update'));
    }

    /**
     * 无公众号，用户注册或登录, (扫码场景：扫码收藏试妆照片 和 扫码测肤)
     * @test
     *
     * 注册：
     * 测肤扫码：先注册微信用户，前端判断是否有born_at, 有的话跳个人首页，没有的话跳到 update()，填写个人信息。
     * @used-by \App\Http\Controllers\Wechat\WechatUserController::update()
     *
     * 收藏试妆照片扫码：先注册微信用户，前端直接跳到： update()，绑定微信用户收藏
     * @used-by \App\Http\Controllers\Wechat\V2_3\FavorController::update()
     *
     * 登录：
     * 测肤扫码链接登录：前端判断是否有born_at, 有的话跳个人首页，没有的话跳到 update()，填写个人信息。
     * 收藏照片链接登录：前端直接跳到： update()，绑定微信用户收藏
     *
     * @param PatchPhoneRequest $request
     * @return array|Response
     * @throws \Exception
     */
    public function patchPhone(PatchPhoneRequest $request)
    {
        $scan_event_id = $request->scan_event_id;
        $phone = $request->phone;
        $customer_id = $request->customer_id;

        /** @var ScanEvent $scan_event */
        $scan_event = (new ScanEvent)->find($scan_event_id);

        if ($scan_event && !$scan_event->isScanned()) { // 无公众号 ，注册
            $wechat_user = $this->appLoginByScanCode($phone, $scan_event);
        } else { // 无公众号，登录
            $wechat_user = $this->loginByH5Page($phone, $customer_id);
        }

        return $this->response(compact('wechat_user', 'scan_event_id'));
    }

    /**
     * 更新地理位置
     * @test
     *
     * @param PatchLocationRequest $request
     *
     * @return array|Response
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function patchLocation(PatchLocationRequest $request)
    {
        /** @var WechatUser $wechat_user */
        $wechat_user = WechatAuth::$wechat_user;

        $address = $this->tryReverseQueryWxUserAddress($request->latitude, $request->longitude);

        $updated = $address
            ? $wechat_user->update($request->only(['latitude', 'longitude']) + $address)
            : false;

        return $this->response(compact('updated', 'wechat_user'));
    }

    /**
     *  新增测肤用户的支付宝账号信息
     * @test
     *
     * @param PatchAliAccountRequest $request
     * @param $openid
     * @return array|\Illuminate\Http\Response
     * @throws \App\Exceptions\ApiAbstractException
     */
    public function patchAliAccount(PatchAliAccountRequest $request, $openid)
    {
        $wechat_user = WechatUser::findByOpenId($openid);
        $this->checkItemExist($wechat_user);

        $updated = $wechat_user->update([
            'ali_account' => $request->ali_account
        ]);

        return $this->response(compact('updated'));
    }

    /**
     * @param WechatUserUpdateRequest $request
     * @return array
     */
    private function assembleWechatUser(WechatUserUpdateRequest $request): array
    {
        $data = [
            'sex' => $request->sex,
        ];

        if ($request->has('name')) {
            $data['name'] = $request->name;
        }

        if ($request->has('age')) {
            $data['age'] = $request->age;
        } elseif ($request->has('born_at')) { // born_at will be deprecated
            $data['born_at'] = $request->born_at;
        }

        if ($request->has('phone')) {
            $data['phone'] = $request->phone;
        }

        return $data;
    }

    /**
     * @see AppWechatUserController::loginQrCode()
     *
     * @param $scan_event_id
     * @param $wechat_user_id
     * @return bool
     * @throws \Exception
     */
    private function updateUserLoginScanEvent($scan_event_id, $wechat_user_id): bool
    {
        /** @var ScanEvent $scan_evt */
        $scan_evt = (new ScanEvent)->find($scan_event_id);
        $this->checkItemExist($scan_evt, trans('wechat.scan_event'));

        $updated = false;
        if (!$scan_evt->isScanned()) {
            $updated = $scan_evt->appLogin($wechat_user_id);
            $scan_evt->incrementWechatUserVisitStore();
        }

        return $updated;
    }

    /**
     *  scan_event事件没处理，1.无公众号，用户注册。
     *
     * @param string $phone
     * @param ScanEvent $scan_event
     * @return WechatUser
     */
    private function appLoginByScanCode(string $phone, ScanEvent $scan_event): WechatUser
    {
        $customer_id = $scan_event->data['customer_id'];
        $lang = $scan_event->data['language'] ?? Language::ZH_CN;
        $store_id = $scan_event->data['store_id'] ?? null;

        $wechat_user = $this->queryOrRegisterWebUser($phone, $customer_id, $lang);

        if ($wechat_user->hasPersonalInfo()) { // 已注册且个人信息，扫码成功
            $scan_event->appLogin($wechat_user->id);
            $scan_event->incrementWechatUserVisitStore();
        } else {
            /**
             * 未填写信息，先记录store_pivot_wechat_user表的信息，H5页面判断进入个人信息页面(上面的update（）)后再更新扫码状态
             *
             * @see update()
             */
            if ($store_id) {
                $this->saveStorePivotWechatUser($wechat_user->id, $store_id);
            }
        }

        return $wechat_user;
    }

    /**
     *  h5页面登录
     *
     * @param $phone
     * @param $customer_id
     *
     * @return WechatUser
     * @throws \Exception
     */
    private function loginByH5Page($phone, $customer_id): WechatUser
    {
        $wechat_user = (new WechatUser)
            ->where('customer_id', $customer_id)
            ->where('phone', $phone)
            ->first();
        $this->checkItemExist($wechat_user, trans('wechat.wechat_user'));

        return $wechat_user;
    }

    /**
     * @param string $phone
     * @param int $customer_id
     * @param string $lang
     * @return WechatUser
     */
    private function queryOrRegisterWebUser(string $phone,
                                            int $customer_id,
                                            string $lang): WechatUser
    {
        // 查询，保存用户数据到wechat_users表
        /** @var WechatUser $wechat_user */
        $wechat_user = (new WechatUser)->firstOrCreate(
            ['phone' => $phone, 'customer_id' => $customer_id], // 无公众号的customer_id
            [
                'authorizer_id' => 0,
                'language'      => $lang,
                'born_at'       => null,
                'openid'        => WechatUser::generateWebOpenid()
            ]
        );

        return $wechat_user;
    }

    /**
     * @param $wechat_user_id
     * @param $store_id
     * @return bool
     */
    private function saveStorePivotWechatUser(int $wechat_user_id, int $store_id): bool
    {
        $store_pivot_wechat_user = (new StorePivotWechatUser)
            ->where(compact('wechat_user_id', 'store_id'))
            ->first();

        $store = (new Store)->find($store_id);
        if (!$store_pivot_wechat_user && $store) {
            $store->wechatUsers()->attach($wechat_user_id);
        }

        return true;
    }

    /**
     * @param float $latitude
     * @param float $longitude
     *
     * @return array|null
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    private function tryReverseQueryWxUserAddress(float $latitude,
                                                  float $longitude): ?array
    {
        try {
            /**
             * @see GeoApiRespMock::geoCodeReverseQuery()
             */
            $res = (new MapApi)->geoCodeReverseQuery($latitude, $longitude);
        } catch (\Exception $exception) {
            return logger($exception->getMessage());
        }

        $addr = $res['result']['addressComponent'] ?? null;
        $format_addr = $res['result']['formatted_address'] ?? null;
        if ($addr && $format_addr) {
            return [
                'country'  => $addr['country'],
                'city'     => $addr['city'],
                'province' => $addr['province'],
                'district' => $addr['district'],
                'address'  => $format_addr,
            ];
        } else {
            return null;
        }
    }

    /**
     * @deprecated
     * @param $wechat_user_store_ids
     * @param $wechat_user
     * @return array|\Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection|static[]
     */
    private function getGoStoresGroupByStoreId($wechat_user_store_ids, $wechat_user)
    {
        $go_stores = (new Store)
            ->leftJoin('skin_records', 'skin_records.store_id', '=', 'stores.id')
            ->whereIn('stores.id', $wechat_user_store_ids)
            ->where('skin_records.wechat_user_id', $wechat_user->id)
            ->whereIn('skin_records.skin_detector_id', [SkinRecord::HM, SkinRecord::WAX])
            ->select(
                'stores.id AS id',
                'skin_records.skin_detector_id AS skin_detector_id',
                DB::raw('COUNT(*) AS skin_record_count')
            )
            ->groupBy('id', 'skin_detector_id')
            ->get();

        $go_stores = $go_stores->groupBy(['id', 'skin_detector_id'])->toArray();
        return $go_stores;
    }
}
